/**
 * @description: 通用工具
 */

const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const esBuild = require('esbuild')
const { init: esmInit, parse: parseImport } = require('es-module-lexer')
const Compiler = require('vue-template-compiler')
const CompilerUtils = require('@vue/component-compiler-utils')
const hash = require('hash-sum')

const nodeModulePrefix = '/@modules/'
const baseCompilerOptions = {
  needMap: false,
  compiler: Compiler,
}

function logger(title = '', msg = '', type = 'error') {
  const tag = type === 'info' ? chalk.cyan.bold(`[simple-vite]`) : chalk.red.bold(`[simple-vite]`)

  return console.log(
    `${chalk.dim(new Date().toLocaleTimeString())} ${tag} ${chalk.green(title)} ${chalk.dim(msg)}`,
  )
}

// 获取文件的内容(string)
const getFileContent = (fileFullPath) => {
  let content = ''

  try {
    content = fs.readFileSync(fileFullPath, 'utf-8')
  } catch (e) {
    logger('read file fail:', e.message)
  }

  return content
}

/**
 * https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/importAnalysis.ts#L254
 * 处理 import，符合 esm 的格式
 * @return {Promise<string>}
 */
const rewriteImport = async (filename = '', code = '') => {
  const dirname = path.dirname(filename)
  let newCode = code
  let imports = []
  try {
    await esmInit
    imports = parseImport(code)[0]
  } catch (e) {
    logger('parseImport fail', e.message)
  }

  /**
   * item: { n: 'vue', s: 17, e: 20, ss: 0, se: 21, d: -1, a: -1 },
   * n 表示模块的名称
   * s 表示模块名称在导入语句中的开始位置
   * e 表示模块名称在导入语句中的结束位置
   * ss 表示导入语句在源代码中的开始位置
   * se 表示导入语句在源代码中的结束位置
   * d 表示导入语句是否为动态导入，如果是则为对应的开始位置，否则默认为 -1
   */
  imports.forEach(({ n: modulePath = '' }) => {
    if (['/', './', '../'].some((prefix) => modulePath.startsWith(prefix))) {
      // 以相对路径开头的处理
      const relativePath = path
        .join(dirname, modulePath)
        .replace(process.cwd(), '')
        .split(path.sep)
        .join('/')
        .split('\\')
        .join('/')

      newCode = newCode.replace(modulePath, `${relativePath}`)
    } else {
      // node_modules 加上 /@module/
      newCode = newCode.replace(modulePath, `${nodeModulePrefix}${modulePath}`)
    }
  })

  return newCode
}

const isPkg = (filePath) => filePath.includes(nodeModulePrefix)

// 获取 node_modules 文件
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/resolve.ts#L328
function resolvePkgFile(filePath) {
  const nodeModulePath = path.join(process.cwd(), 'node_modules')
  const pkgPath = path.join(nodeModulePath, filePath.replace(nodeModulePrefix, ''))

  try {
    // 特殊处理下，加载完全版的 vue
    if (filePath === `${nodeModulePrefix}vue`) {
      return path.join(pkgPath, 'dist/vue.esm.js')
    }
    const pkgJson = require(path.join(pkgPath, 'package.json'))
    const entryPoint = pkgJson.module || pkgJson.main || 'index.js'
    return path.join(pkgPath, entryPoint)
  } catch (e) {
    logger('ResolvePkgFile fail:', e.message)
  }
}

function styleLoader(code) {
  return `
        const css = ${JSON.stringify(code)}
        const style = document.createElement('style')
        style.innerHTML = css
        document.head.appendChild(style)
      `
}

/**
 * 处理不同类型的文件， 返回浏览器能解析的代码
 * @return {{type: string, code: string}}
 */
const getResContent = async (fileFullPath, pathname, query = {}) => {
  const respond = {
    needRewriteImport: true,
    type: 'text/javascript',
    code: '',
  }

  const extname = path.extname(fileFullPath)
  switch (extname) {
    // 处理css，esm 并不能解析css
    case '.css':
      respond.code = styleLoader(getFileContent(fileFullPath))
      break

    // 处理静态资源
    case '.svg':
    case '.png':
      // 这里要根据原来文件路径先转化成绝对路径， 先简单处理下
      respond.code = fs.readFileSync('./src/img/logo.svg')
      respond.type = {
        '.svg': 'image/svg+xml',
        '.png': 'image/png',
      }[extname]
      respond.needRewriteImport = false
      break

    case '.js':
      respond.code = getFileContent(fileFullPath)
      break

    case '.ts':
      // 将 ts 代码转 js 代码
      const code = getFileContent(fileFullPath)
      let result = {}
      try {
        result = esBuild.transformSync(code, { loader: 'ts' })
      } catch (e) {
        logger('getResContent fail: ', e.message)
      }

      if (result.warnings && result.warnings.length) logger('esbuild warnings', result.warnings)
      respond.code = result.code
      break

    // 如 vue-loader 一样处理 sfc 文件
    case '.vue':
      const sfc = CompilerUtils.parse({
        ...baseCompilerOptions,
        source: getFileContent(fileFullPath),
      })
      const scopeId = hash(fileFullPath)

      // 没有type 则是请求的 .vue 文件
      if (!query.type) {
        const scriptStr = sfc.script.content
        const style = sfc.styles[0]
        const styleQuery = ['type=style']

        style.scoped && styleQuery.push('scoped=true')
        style.lang && styleQuery.push(`lang=${style.lang}`)

        const script = scriptStr.replace('export default ', 'const _vue_comp = ')

        respond.code = `
          ${script}
          import {render as _render} from '${pathname}?type=template'
          import '${pathname}?${styleQuery.join('&')}'
          _vue_comp.render = _render
          _vue_comp._scopeId = "data-v-${scopeId}"
          export default _vue_comp
        `
        respond.needRewriteImport = false
      } else if (query.type === 'template') {
        // 将 templateStr 编译成 render 函数

        const tpl = sfc.template.content
        const compile = CompilerUtils.compileTemplate({ ...baseCompilerOptions, source: tpl })

        // 变成 es6 语法
        respond.code = compile.code.replace('var render ', 'export const render ')
      } else if (query.type === 'style') {
        // 最后处理 less 文件
        const style = sfc.styles[0]
        const compileStyle = CompilerUtils.compileStyle({
          source: style.content,
          preprocessLang: query.lang,
          trim: true,
          scoped: query.scoped === 'true',
          id: `data-v-${scopeId}`,
        })

        respond.code = styleLoader(compileStyle.code)
      }
      break

    default:
      break
  }

  return respond
}

module.exports = {
  logger,
  getFileContent,
  getResContent,
  rewriteImport,
  isPkg,
  resolvePkgFile,
}
